iT邦幫忙

2021 iThome 鐵人賽

DAY 12
1
自我挑戰組

登堂入室!前端工程師的觀念技術 _30_ 題系列 第 12

11. 解釋 Callback Hell & 使用Promise

  • 分享至 

  • xImage
  •  

今天也是非同步資料請求的主題,複習一下在XHR(XMLHttpRequest)搭配Promise的用法!

Callback & Callback Hell


如果一個function被作為參數傳進另一個function,這種行為稱為"Callback",
作為參數的function 則稱作"callback function"。

來看個例子:

doSomething(function(result){console.log(result);}, failureCallback)

假設doSomething()執行的是,回傳成功執行參數1(匿名函式),回傳失敗則執行參數2(failureCallback)。

被傳入doSomething()作為參數的兩個function,就被稱作callback function。

但當我們在函式裡執行更多行為時,被嵌套的callback會越來越多層,然後程式碼會變成這樣的結構:

function failureCallback() {
  console.log("failed");
}

doSomething(function(result) {
  doSecondThing(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

這種 Callback被一層層嵌套,導致 程式碼難以閱讀和維護 的情況,就被稱作 Callback Hell。*

或是例子不夠生動,可以看看網路上的圖片:
https://ithelp.ithome.com.tw/upload/images/20210912/20129476WBMI112r7I.jpg

而為了避免產生callback Hell,現在多以使用promise的方式取代。

Promise


定義

Promise 物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值。
引用自Promise - JavaScript | MDN

意思是, Promise 代表 在進行非同步請求時,成功取得的資料失敗的物件

每當一個host要連結資料庫(database management system),資料庫應該回傳一個Promise。

Promise 的建構式語法:

new Promise( /* executor */ function(resolve, reject) { ... } );

new是指產生一個新物件,所以這裡會產生一個Promise的空物件,Promise 裡面包含 resolve 或 reject 兩種 callback function:

  • resolve 為成功取得的資料時,回傳資料。
  • reject 則會在失敗時回傳 error object。

每當傳入這兩個參數,executor函式就會直接執行,並在成功時執行resolve,在失敗時執行reject

而promise很常被搭配使用兩個function:

  • .then() 接住 resolve 的結果。
  • .catch() 接住 reject 的結果。

舉例:

// 設定回傳resolve的情況
let example = new Promise((resolve, reject) => {
	resolve({type: "object"});
});

example.then((data)=>{console.log(data);});

在這個範例裡,我們設定resolve會回傳一個object。
並且在執行.then()後,把資料印出來。

// 設定回傳reject的情況
let example = new Promise((resolve, reject) => {
	reject(new Error("not allowed"));
});

example
  .then((data)=>{console.log(data);})
  .catch((error) => {console.log(error);});

而這個範例,是設定在reject時,產生一個error object。
並在.catch()抓到錯誤後,印出錯誤訊息。

promise的function是可以被串接起來的,被稱為 Promise Chain。

let example = new Promise((resolve, reject) => {
	resolve({ string : "Hello" });
});

example
  .then((data)=>{console.log(data);})
  .then(() => {console.log("HI");})
  .catch((error) => {console.log(error);});  
  
  // output: { string : "Hello" } ; "HI"

這樣的好處是,不管接續執行多少工作(.then()),最後都只要指定一次失敗的情況(.catch())。
而不必像早期的方式(Callback Hell的範例),傳入許多次failureCallback

看完範例,最後來嘗試實際應用Promise。

Promise with XHR


延續昨天顯示氣溫的例子,今天試著用Promise改寫內容。

附上程式碼(和原來寫法的差異,寫在註解裡):

  const section = document.querySelector('section');

  // 獲得URL; 請記得將授權碼字樣替換掉!
  var requestURL = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-D0047-063?Authorization=授權碼&limit=1&offset=7&format=JSON&elementName=T';

  function loadData(url){
    
     return new Promise(function(resolve, reject) {
       
        var request = new XMLHttpRequest();
        request.open('GET', requestURL);
        request.responseType = 'json'; 
        
        request.onload = function() {
          // 如果正確獲得資料,將資料寫進resolve
          if (request.status === 200) {
            resolve(request.response);
          } else {
            // 如果失敗,回傳錯誤訊息
            reject(Error('error code:' + request.statusText));
          }
        }
      
        request.send(); 
    });
  }

  // promise chain
  loadData(requestURL)
    .then((response)=>showWeather(response))
    .catch((error)=>console.log(error));

  function showWeather(jsonObj) {
      var jsonT = jsonObj['records']['locations'][0]['location'][0]['weatherElement'][0]['time'][0]['elementValue'][0]['value'];
      const temperature = document.createElement('h1');
      temperature.textContent = "地點: 台北市, 現在氣溫: " + jsonT + ' °C ';
      section.appendChild(temperature);
  } 

運行結果:
https://ithelp.ithome.com.tw/upload/images/20210912/20129476yDP4QOpTM1.jpg

結論

附上一些和Promise相關的問題:

【如內文有誤還請不吝指教>< 謝謝閱覽至此的各位:D】

-----正文結束-----


上一篇
10. 解釋 AJAX 的工作原理(XMLHttpRequest)
下一篇
12. 使用 async & await (Fetch API)
系列文
登堂入室!前端工程師的觀念技術 _30_ 題31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言